﻿using BrightIdeasSoftware;
using ComponentFactory.Krypton.Toolkit;
using LoRAExplorer.Framework;
using StableDiffusionTools.Controls.KryptonHelper;
using StableDiffusionTools.Forms;
using StableDiffusionTools.Framework;
using StableDiffusionTools.Imaging;
using StableDiffusionTools.Themes;
using StableDiffusionTools.Utils;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Data;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace LoRAExplorer.Forms
{
    [Export(typeof(IForm))]
    [ExportMetadata("Name", "Lora Explorer")]
    [ExportMetadata("Image", "searchfolder32")]
    public partial class LoraExplorerForm : KryptonForm, IForm
    {
        public bool Busy { get; protected set; }
        private SettingsManager<LoRAExplorerSettings> settingsMan = new SettingsManager<LoRAExplorerSettings>(nameof(LoRAExplorerSettings), createBackups: false);
        LoRAExplorerSettings settings;
        private TypedObjectListView<SafetensorTag> tolvTags;
        private readonly List<char[]> ClipSplit = new List<char[]>() { new char[] { ',', ' ' }, new char[] { ',' } };
        private ModelFilter filter;
        private string filterString = "";
        private FilterType filterType = FilterType.Metadata;
        //private string currentPath;
        private Image importImage = StableDiffusionTools.Properties.Resources.document_import16;
        private Image cancelImage = BitmapResizer.ResizeBitmap(StableDiffusionTools.Properties.Resources.stop32, new BitmapResizeGDISettings(GDIResizePresent.Default, new Size(16, 16)));
        CancellationTokenSource source = null;
        private bool hasLoadedMetaTab = false;
        private bool hasLoadedTagsTab = false;

        public LoraExplorerForm()
        {
            InitializeComponent();
            GlobalTheme.Init();
            buttonSpecTheme.KryptonContextMenu = new KryptonThemeContextMenu();

            Icon = Properties.Resources.searchfolder;
            tsbBrowse.Image = StableDiffusionTools.Properties.Resources.folder16;
            tsbLoad.Image = importImage;
            tsbFilter.Image = StableDiffusionTools.Properties.Resources.filter16;

            imageListSmall.Images.Add(true.ToString(), StableDiffusionTools.Properties.Resources.statusok16);
            imageListSmall.Images.Add(false.ToString(), StableDiffusionTools.Properties.Resources.statusfail16);
            imageListSmall.Images.Add(Safetensors.Result.Empty.ToString(), StableDiffusionTools.Properties.Resources.error16);
            imageListSmall.Images.Add(Safetensors.Result.Missing.ToString(), StableDiffusionTools.Properties.Resources.statusfail16);
            imageListSmall.Images.Add(Safetensors.Result.Found.ToString(), StableDiffusionTools.Properties.Resources.statusok16);
            imageListSmall.Images.Add(Safetensors.Result.Failed.ToString(), StableDiffusionTools.Properties.Resources.error16);
            settings = settingsMan.LoadSettings();
            imageControl1.SizeMode = settings.FitSize ? StableDiffusionTools.Controls.ImageSizeMode.Fit : StableDiffusionTools.Controls.ImageSizeMode.Zoom;
            filterType = settings.LastFilterType;

            tolvTags = new TypedObjectListView<SafetensorTag>(olvTags);

            olvColumnMeta.AspectToStringConverter = (e) => string.Empty;
            olvColumnMeta.GroupKeyToTitleConverter = (e) => e as string;
            TypedColumn<SafetensorTag> tMeta = new TypedColumn<SafetensorTag>(olvColumnMeta)
            {
                AspectGetter = (e) =>
                {
                    return e?.HasMetadata;
                },
                ImageGetter = (e) =>
                {
                    return e?.HasMetadata.ToString();
                },
                GroupKeyGetter = (e) =>
                {
                    if (e == null)
                        return null;
                    return (e.HasMetadata ? "Includes Metadata" : "Lacking Metadata");
                },
            };
            olvColumnMeta.Renderer = new BaseRenderer() { UseGdiTextRendering = false };

            olvColumnTags.AspectToStringConverter = (e) => string.Empty;
            olvColumnTags.GroupKeyToTitleConverter = (e) => e as string;
            TypedColumn<SafetensorTag> tTags = new TypedColumn<SafetensorTag>(olvColumnTags)
            {
                AspectGetter = (e) =>
                {
                    return e?.HasTags;
                },
                ImageGetter = (e) =>
                {
                    return e?.HasTags.ToString();
                },
                GroupKeyGetter = (e) =>
                {
                    if (e == null)
                        return null;
                    return (e.HasTags ? "Includes Tags" : "Lacking Tags");
                },
            };
            olvColumnTags.Renderer = new BaseRenderer() { UseGdiTextRendering = false };

            olvColumnCover.AspectToStringConverter = (e) => string.Empty;
            olvColumnCover.GroupKeyToTitleConverter = (e) => e as string;
            TypedColumn<SafetensorTag> cTags = new TypedColumn<SafetensorTag>(olvColumnCover)
            {
                AspectGetter = (e) =>
                {
                    return e?.CoverImages != null;
                },
                ImageGetter = (e) =>
                {
                    return (e?.CoverResult);
                },
                GroupKeyGetter = (e) =>
                {
                    if (e == null)
                        return null;

                    return ($"Cover {e.CoverResult}");
                },
            };
            olvColumnCover.Renderer = new BaseRenderer() { UseGdiTextRendering = false };


            tolvTags.GetColumn("File").AspectGetter += (o) => o?.FileName;
            olvColumnFile.Renderer = new BaseRenderer() { UseGdiTextRendering = false };

            tolvTags.GetColumn("Model").AspectGetter += (o) => o?.Model;
            olvColumnModel.Renderer = new BaseRenderer() { UseGdiTextRendering = false };

            filter = new ModelFilter(delegate (object x)
            {
                SafetensorTag tag = (SafetensorTag)x;
                switch (filterType)
                {
                    case FilterType.Filename:
                        return tag.FileName.IndexOf(filterString, StringComparison.OrdinalIgnoreCase) != -1;
                    case FilterType.Model:
                        return tag.Model.IndexOf(filterString, StringComparison.OrdinalIgnoreCase) != -1;
                    case FilterType.Metadata:
                    default:
                        return tag.Metadata.IndexOf(filterString, StringComparison.OrdinalIgnoreCase) != -1;
                }
            });

            coverImagesToolStripMenuItem.DropDown.Closing += DropDown_Closing;

            FormClosing += (s, e) =>
            {
                imageControl1.Image = null;
                settings.FitSize = imageControl1.SizeMode == StableDiffusionTools.Controls.ImageSizeMode.Fit;
                settings.LastFilterType = filterType;
                settings.LastSidePanelWidth = kryptonSplitContainer1.ClientSize.Width - kryptonSplitContainer1.Panel1.ClientSize.Width;
                settings.LastSidePanelHeight = kryptonSplitContainer2.SplitterDistance;
                settingsMan.SaveSettings(settings);
            };

            olvTags.PrimarySortColumn = olvColumnFile;
            olvTags.PrimarySortOrder = SortOrder.Descending;

            if (settings.LastSidePanelWidth != -1)
                kryptonSplitContainer1.SplitterDistance = kryptonSplitContainer1.ClientSize.Width - settings.LastSidePanelWidth;
            if (settings.LastSidePanelHeight != -1)
                kryptonSplitContainer2.SplitterDistance = settings.LastSidePanelHeight;
        }


        private async void tstbPath_KeyDown(object sender, KeyEventArgs e)
        {
            if (!Busy && e.KeyCode == Keys.Enter)
            {
                e.SuppressKeyPress = true;
                PathInfo info = ExtendedPathInfo.GetPathInfo(tstbPath.Text);
                if (info == PathInfo.Directory)
                {
                    await LoadPath(tstbPath.Text);
                }
                else if (info == PathInfo.File)
                {
                    await LoadPaths(new string[] { tstbPath.Text }, true);
                }
                else if (info == PathInfo.NotFound)
                {
                    ClearTags();
                    tsslStatus.Text = $"Cleared";
                    tspbProgress.Value = 0;
                }
            }
        }

        private void tsbBrowse_MouseUp(object sender, MouseEventArgs e)
        {
            var folder = new FolderSelectDialog();
            if (!string.IsNullOrEmpty(settings.LastPath))
                folder.InitialDirectory = settings.LastPath;

            if (folder.ShowDialog() && !IsDisposed)
            {
                settings.LastPath = tstbPath.Text = folder.FileName;
            }
        }

        private async void tsbLoad_MouseUp(object sender, MouseEventArgs e)
        {
            if (!Busy)
            {
                PathInfo info = ExtendedPathInfo.GetPathInfo(tstbPath.Text);
                if (info == PathInfo.Directory)
                {
                    await LoadPath(tstbPath.Text);
                }
                else if (info == PathInfo.File)
                {
                    await LoadPaths(new string[] { tstbPath.Text }, true);
                }
                else if (info == PathInfo.NotFound)
                {
                    ClearTags();
                    tsslStatus.Text = $"Cleared";
                    tspbProgress.Value = 0;
                }
            }
            else
            {
                source?.Cancel();
            }
        }

        private void kryptonBTNExportCsvAs_Click(object sender, EventArgs e)
        {
            SafetensorTag tag = tolvTags.SelectedObject;
            if (tag != null && tag.HasTags)
            {
                using (var tagExportDialog = new TagExportDialog<ExportOrder>("Preview", "Export CSV"))
                {
                    void resultRequested(object s, TagExportDialog<ExportOrder>.ResultRequestEventArgs e2)
                    {
                        settings.LastExportOrder = e2.SelectedEnum;
                        settings.LastExportSortDescending = e2.SortDescending;
                        tagExportDialog.Text = tag.ExportCSV(e2.SelectedEnum, e2.SortDescending);
                    }
                    tagExportDialog.ResultRequested += resultRequested;
                    if (tagExportDialog.ShowDialog(this, settings.LastExportOrder, settings.LastExportSortDescending) == DialogResult.OK)
                    {
                        using (var browseFile = new SaveFileDialog())
                        {
                            browseFile.InitialDirectory = Path.GetDirectoryName(tag.FullName);
                            browseFile.FileName = Path.GetFileNameWithoutExtension(tag.FileName) + "_tags";
                            browseFile.Filter = "CSV files (*.csv)|*.csv|All files (*.*)|*.*";
                            if (browseFile.ShowDialog(this) == DialogResult.OK)
                            {
                                if (!string.IsNullOrEmpty(browseFile.FileName))
                                {
                                    try
                                    {
                                        using (var fileStream = File.OpenWrite(browseFile.FileName))
                                        using (var fileWriter = new StreamWriter(fileStream))
                                        {
                                            fileWriter.Write(tagExportDialog.Text);
                                        }
                                    }
                                    catch (Exception ex)
                                    {
                                        KryptonMessageBox.Show(ex.Message, "Error");
                                    }
                                }
                            }
                        }
                    }

                    tagExportDialog.ResultRequested -= resultRequested;
                }
            }
        }

        private void kryptonBTNExportTxtAs_Click(object sender, EventArgs e)
        {
            SafetensorTag tag = tolvTags.SelectedObject;
            if (tag != null && tag.HasTags)
            {
                using (var tagExportDialog = new TagExportDialog<ExportOrder>("Preview", "Export Text"))
                {
                    void resultRequested(object s, TagExportDialog<ExportOrder>.ResultRequestEventArgs e2)
                    {
                        settings.LastExportOrder = e2.SelectedEnum;
                        settings.LastExportSortDescending = e2.SortDescending;
                        tagExportDialog.Text = tag.ExportTxt(e2.SelectedEnum, e2.SortDescending);
                    }

                    tagExportDialog.ResultRequested += resultRequested;
                    if (tagExportDialog.ShowDialog(this, settings.LastExportOrder, settings.LastExportSortDescending) == DialogResult.OK)
                    {
                        using (var browseFile = new SaveFileDialog())
                        {
                            browseFile.InitialDirectory = Path.GetDirectoryName(tag.FullName);
                            browseFile.FileName = Path.GetFileNameWithoutExtension(tag.FileName) + "_tags";
                            browseFile.Filter = "txt files (*.txt)|*.txt|All files (*.*)|*.*";
                            if (browseFile.ShowDialog(this) == DialogResult.OK)
                            {
                                if (!string.IsNullOrEmpty(browseFile.FileName))
                                {
                                    try
                                    {
                                        using (var fileStream = File.OpenWrite(browseFile.FileName))
                                        using (var fileWriter = new StreamWriter(fileStream))
                                        {
                                            fileWriter.Write(tagExportDialog.Text);
                                        }
                                    }
                                    catch (Exception ex)
                                    {
                                        KryptonMessageBox.Show(ex.Message, "Error");
                                    }
                                }
                            }
                        }
                    }

                    tagExportDialog.ResultRequested -= resultRequested;
                }
            }
        }

        private void olvTags_SelectedIndexChanged(object sender, EventArgs e)
        {
            hasLoadedMetaTab = false;
            hasLoadedTagsTab = false;

            SafetensorTag tag = tolvTags.SelectedObject;
            UpdateTabPagesFromTag(tag);

            if (tag == null)
            {
                kryptonTBMetadata.Clear();

                kryptonTBTags.Clear();
                kryptonBTNExportCsvAs.Enabled = kryptonBTNExportTxtAs.Enabled = false;

                imageControl1.Image = null;
                return;
            }

            if (!string.IsNullOrEmpty(tag.ImagePath))
            {
                try
                {
                    imageControl1.SetImage(ImageUtil.LoadBitmap(tag.ImagePath), true);
                }
                catch (Exception)
                {
                    imageControl1.Image = null;
                }
            }
            else
            {
                imageControl1.Image = null;
            }
        }

        private void olvTags_DragEnter(object sender, DragEventArgs e)
        {
            if (e.Data.GetDataPresent(DataFormats.FileDrop) && !Busy)
            {
                e.Effect = DragDropEffects.Copy;
            }
            else
            {
                e.Effect = DragDropEffects.None;
            }
        }

        private async void olvTags_DragDrop(object sender, DragEventArgs e)
        {
            if (!Busy)
            {
                if (e.Data.GetDataPresent(DataFormats.FileDrop))
                {
                    string[] files = (string[])e.Data.GetData(DataFormats.FileDrop);
                    if (files.Length == 1 && ExtendedPathInfo.GetPathInfo(files[0]) == PathInfo.Directory)
                    {
                        tstbPath.Text = files[0];
                        await LoadPath(files[0]);
                    }
                    else
                    {
                        await LoadPaths(files);
                    }
                }
            }
        }

        private async void olvTags_KeyDown(object sender, KeyEventArgs e)
        {
            if (e.KeyCode == Keys.Escape)
            {
                olvTags.DeselectAll();
                e.SuppressKeyPress = true;
            }
            else if (e.KeyCode == Keys.V && e.Control)
            {
                if (Clipboard.ContainsFileDropList())
                {
                    e.SuppressKeyPress = true;
                    string[] files = (string[])Clipboard.GetData(DataFormats.FileDrop);
                    if (files.Length == 1 && ExtendedPathInfo.GetPathInfo(files[0]) == PathInfo.Directory)
                    {
                        tstbPath.Text = files[0];
                        await LoadPath(files[0]);
                    }
                    else
                    {
                        await LoadPaths(files);
                    }
                }
            }
        }

        private void ctxTags_Opening(object sender, System.ComponentModel.CancelEventArgs e)
        {
            SafetensorTag tag = tolvTags.SelectedObject;
            if (tag != null)
            {
                bool hasCoverImages = tag.CoverImages != null;

                //clear handlers
                coverImagesToolStripMenuItem.Click -= coverImages_Handler;
                for (int i = coverImagesToolStripMenuItem.DropDownItems.Count - 1; i >= 0; i--)
                {
                    coverImagesToolStripMenuItem.DropDownItems[i].Click -= coverImages_Handler;
                    coverImagesToolStripMenuItem.DropDownItems.RemoveAt(i);
                }

                if (hasCoverImages)
                {
                    coverImagesToolStripMenuItem.Text = $"Embedded cover{(tag.CoverImages.Length > 1 ? $"({tag.CoverImages.Length})" : "")}";
                    if (tag.CoverImages.Length == 1)
                    {
                        coverImagesToolStripMenuItem.Click += coverImages_Handler;
                    }
                    else
                    {
                        for (int i = 0; i < tag.CoverImages.Length; i++)
                        {
                            ToolStripMenuItem itm = coverImagesToolStripMenuItem.DropDownItems.Add($"{i + 1}") as ToolStripMenuItem;
                            itm.Tag = i;
                            itm.Click += coverImages_Handler;
                        }
                    }
                }

                coverImagesToolStripMenuItem.Visible = hasCoverImages;
            }
            else
            {
                e.Cancel = true;
            }
        }

        private void coverImages_Handler(object sender, EventArgs e)
        {
            SafetensorTag tag = tolvTags.SelectedObject;
            if (tag != null && tag.CoverImages != null)
            {
                try
                {
                    int coverIndex = 0;
                    if (((ToolStripMenuItem)sender).Tag is int tagIndex)
                        coverIndex = tagIndex;

                    imageControl1.SetImage(tag.CoverImages[coverIndex], false);
                }
                catch { }
            }
        }

        private void openExplorerToolStripMenuItem_Click(object sender, EventArgs e)
        {
            ExplorerUtil.Open(tolvTags.SelectedObject?.FullName, !ModifierKeys.HasFlag(Keys.Shift));
        }

        private void DropDown_Closing(object sender, ToolStripDropDownClosingEventArgs e)
        {
            if (e.CloseReason == ToolStripDropDownCloseReason.ItemClicked)
            {
                e.Cancel = true;
            }
        }

        private void tsbFilter_MouseUp(object sender, MouseEventArgs e)
        {
            using (var filterDialogEnum = new InputDialogWithEnum<Framework.FilterType>("Filter list by keyword", "Filter", filterString, true, "By type", filterType))
            {
                if (filterDialogEnum.ShowDialog() == DialogResult.OK)
                {
                    if (!string.IsNullOrWhiteSpace(filterDialogEnum.Result))
                    {
                        SetFilter(filterDialogEnum.Result, filterDialogEnum.ResultEnum);
                    }
                    else
                    {
                        RemoveFilter(filterDialogEnum.ResultEnum);
                    }
                }
            }
        }

        private void kryptonNavigator1_SelectedPageChanged(object sender, EventArgs e)
        {
            UpdateTabPagesFromTag(tolvTags.SelectedObject);
        }

        private async Task LoadPaths(string[] files, bool clearExisting = false)
        {
            source = new CancellationTokenSource();
            SetEnabledState(false);

            Progress<CountProgress> progress = new Progress<CountProgress>();
            void progressChanged(object s, CountProgress c)
            {
                tsslStatus.Text = $"Loading... {c.Completed} Model files found";
                tspbProgress.Value = c.GetProgressInt();
            }
            progress.ProgressChanged += progressChanged;

            if (clearExisting)
                ClearTags();

            List<SafetensorTag> tags = await LoadPaths(files, progress, 4, source.Token);
            if (tags.Count > 0)
                AssignTags(tags);

            progress.ProgressChanged -= progressChanged;
            if (!source.Token.IsCancellationRequested)
            {
                tsslStatus.Text = $"Loaded {tags.Count} Model files";
                tspbProgress.Value = 100;
            }
            else
            {
                tsslStatus.Text = $"Load was canceled";
                tspbProgress.Value = 0;
            }

            SetEnabledState(true);
        }

        private async Task<List<SafetensorTag>> LoadPaths(string[] paths, IProgress<CountProgress> progress, int maxDegreeOfParallelism = 1, CancellationToken cancellation = default(CancellationToken))
        {
            List<SafetensorTag> resultTags = await Task.Run(() =>
            {
                object locker = new object();
                int counter = 0;
                int errored = 0;

                Queue<string> dirs = new Queue<string>();
                List<string> safetensors = new List<string>();

                for (int i = 0; i < paths.Length; i++)
                {
                    try
                    {
                        if (cancellation.IsCancellationRequested)
                            break;

                        if (Path.GetExtension(paths[i]).Equals(".safetensors", StringComparison.InvariantCultureIgnoreCase))
                        {
                            safetensors.Add(paths[i]);
                        }
                        else if (ExtendedPathInfo.GetPathInfo(paths[i]) == PathInfo.Directory)
                        {
                            dirs.Enqueue(paths[i]);
                        }
                    }
                    catch (Exception) { }
                }

                while (dirs.Count > 0)
                {
                    string item = dirs.Dequeue();
                    var info = ExtendedPathInfo.GetPathInfo(item);
                    if (info == PathInfo.Directory)
                    {
                        try
                        {
                            string[] itemDirs = Directory.GetDirectories(item);
                            for (int i = 0; i < itemDirs.Length; i++)
                            {
                                dirs.Enqueue(itemDirs[i]);
                            }
                        }
                        catch (Exception) { }
                    }
                    try
                    {
                        string[] files = Directory.GetFiles(item);
                        for (int i = 0; i < files.Length; i++)
                        {
                            if (cancellation.IsCancellationRequested)
                                break;

                            if (Path.GetExtension(files[i]).Equals(".safetensors", StringComparison.InvariantCultureIgnoreCase))
                            {
                                safetensors.Add(files[i]);
                            }
                        }
                    }
                    catch (Exception) { }
                }

                ConcurrentBag<SafetensorTag> tags = new ConcurrentBag<SafetensorTag>();
                Parallel.ForEach(safetensors, new ParallelOptions { MaxDegreeOfParallelism = maxDegreeOfParallelism }, (t) =>
                {
                    if (cancellation.IsCancellationRequested)
                        return;

                    try
                    {
                        tags.Add(new SafetensorTag(t));
                    }
                    catch (Exception)
                    {
                        errored++;
                    }

                    if (progress != null)
                    {
                        int currentCount = Interlocked.Increment(ref counter);
                        if (currentCount % 25 == 0)
                        {
                            lock (locker)
                            {
                                progress.Report(new CountProgress(safetensors.Count, currentCount));
                            }
                        }
                    }
                });

                if (cancellation.IsCancellationRequested)
                    return new List<SafetensorTag>();

                return tags.OrderBy((t) => t.FullName, new CustomComparer<string>(CompareUtil.CompareNatural)).ToList();
            });
            return resultTags;
        }

        private async Task LoadPath(string path)
        {
            source = new CancellationTokenSource();
            SetEnabledState(false);

            settings.LastPath = path;

            Progress<CountProgress> progress = new Progress<CountProgress>();
            void progressChanged(object s, CountProgress c)
            {
                tsslStatus.Text = $"Loading... {c.Completed} Model files found";
                tspbProgress.Value = c.GetProgressInt();
            }
            progress.ProgressChanged += progressChanged;

            ClearTags();
            List<SafetensorTag> tags = await LoadPath(tstbPath.Text, progress, 4, source.Token);
            if (tags.Count > 0)
                AssignTags(tags);

            progress.ProgressChanged -= progressChanged;
            if (!source.Token.IsCancellationRequested)
            {
                tsslStatus.Text = $"Loaded {tags.Count} Model files";
                tspbProgress.Value = 100;
            }
            else
            {
                tsslStatus.Text = $"Load was canceled";
                tspbProgress.Value = 0;
            }

            SetEnabledState(true);
        }

        private async Task<List<SafetensorTag>> LoadPath(string path, IProgress<CountProgress> progress = null, int maxDegreeOfParallelism = 1, CancellationToken cancellation = default(CancellationToken))
        {
            List<SafetensorTag> resultTags = await Task.Run(() =>
            {
                object locker = new object();
                int counter = 0;
                int errored = 0;

                Queue<string> dirs = new Queue<string>(new string[] { path });
                List<string> safetensors = new List<string>();
                while (dirs.Count > 0)
                {
                    string item = dirs.Dequeue();
                    var info = ExtendedPathInfo.GetPathInfo(item);
                    if (info == PathInfo.Directory)
                    {
                        try
                        {
                            string[] itemDirs = Directory.GetDirectories(item);
                            for (int i = 0; i < itemDirs.Length; i++)
                            {
                                dirs.Enqueue(itemDirs[i]);
                            }
                        }
                        catch (Exception) { }
                    }
                    try
                    {
                        string[] files = Directory.GetFiles(item);
                        for (int i = 0; i < files.Length; i++)
                        {
                            if (cancellation.IsCancellationRequested)
                                break;

                            if (Path.GetExtension(files[i]).Equals(".safetensors", StringComparison.InvariantCultureIgnoreCase))
                            {
                                safetensors.Add(files[i]);
                            }
                        }
                    }
                    catch (Exception) { }
                }

                ConcurrentBag<SafetensorTag> tags = new ConcurrentBag<SafetensorTag>();
                Parallel.ForEach(safetensors, new ParallelOptions { MaxDegreeOfParallelism = maxDegreeOfParallelism }, (t) =>
                {
                    if (cancellation.IsCancellationRequested)
                        return;

                    try
                    {
                        tags.Add(new SafetensorTag(t));
                    }
                    catch (Exception)
                    {
                        errored++;
                    }

                    if (progress != null)
                    {
                        int currentCount = Interlocked.Increment(ref counter);
                        if (currentCount % 25 == 0)
                        {
                            lock (locker)
                            {
                                progress.Report(new CountProgress(safetensors.Count, currentCount));
                            }
                        }
                    }
                });

                if (cancellation.IsCancellationRequested)
                    return new List<SafetensorTag>();

                return tags.OrderBy((t) => t.FullName, new CustomComparer<string>(CompareUtil.CompareNatural)).ToList();
            });
            return resultTags;
        }

        private void SetFilter(string filterOn, Framework.FilterType type)
        {
            filterString = filterOn;
            filterType = type;
            olvTags.ModelFilter = filter;
        }

        private void RemoveFilter() => RemoveFilter(filterType);
        private void RemoveFilter(Framework.FilterType type)
        {
            filterType = type;
            filterString = "";
            olvTags.ModelFilter = null;
        }

        private void SetEnabledState(bool enabled)
        {
            tsbBrowse.Enabled = enabled;
            tsbLoad.Enabled = enabled;
            tsbFilter.Enabled = enabled;
            Busy = !enabled;
        }

        private void ClearTags()
        {
            var items = tolvTags.Objects;
            for (int i = 0; i < items.Count; i++)
            {
                items[i].DisposeImages();
            }

            olvTags.SuspendLayout();
            olvTags.BeginUpdate();
            olvTags.ClearObjects();
            RemoveFilter();
            olvTags.ResumeLayout();
            olvTags.EndUpdate();
        }

        private void AssignTags(List<SafetensorTag> tags)
        {
            olvTags.SuspendLayout();
            olvTags.BeginUpdate();
            olvTags.AddObjects(tags);
            olvTags.ResumeLayout();
            olvTags.EndUpdate();
        }

        private void UpdateTabPagesFromTag(SafetensorTag tag)
        {
            if (tag != null)
            {
                if (kryptonNavigator1.SelectedPage == kryptonPageMeta)
                {
                    if (!hasLoadedMetaTab)
                    {
                        hasLoadedMetaTab = true;
                        kryptonTBMetadata.Text = tag.Metadata;
                    }
                }
                else if (kryptonNavigator1.SelectedPage == kryptonPageTags)
                {
                    if (!hasLoadedTagsTab)
                    {
                        hasLoadedTagsTab = true;
                        kryptonTBTags.Text = tag.Tags;
                        kryptonBTNExportCsvAs.Enabled = kryptonBTNExportTxtAs.Enabled = tag.HasTags;
                    }
                }
            }
            else
            {
                kryptonTBMetadata.Clear();
                kryptonTBTags.Clear();
                kryptonBTNExportCsvAs.Enabled = kryptonBTNExportTxtAs.Enabled = false;
            }
        }

        public bool CloseFromTaskTray()
        {
            if (Disposing || IsDisposed)
                return true;

            Close();
            return true;
        }
    }
}
